package com.mymall.serve.config.redission.aop;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mymall.serve.config.redission.annotation.LocalLock;
import com.mymall.serve.exception.BusException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 本地锁切面
 *
 * @author 魏巍
 * @since 2020/9/28
 */
@Slf4j
@Aspect
@Configuration
public class LockMethodInterceptor {

    private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()
            // 最大缓存 1000 个
            .maximumSize(1000)
            // 设置写缓存后 5 秒过期
            .expireAfterWrite(10, TimeUnit.SECONDS)
            .build();

    @Around("execution(public * *(..)) && @annotation(com.mymall.serve.config.redission.annotation.LocalLock)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        LocalLock localLock = method.getAnnotation(LocalLock.class);
        String lockKey = localLock.key();
        if (StringUtils.isBlank(lockKey)) {
            throw new BusException("lockKey不能为空");
        }

        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) pjp.getSignature()).getMethod());
        Object[] args = pjp.getArgs();

//        String key = getKey(localLock.key(), pjp.getArgs());
        String key = getVauleBySpel(lockKey,parameterNames,args,localLock.keyConstant());
        log.info("lockKey:{}",key);
        if (!StringUtils.isEmpty(key)) {
            // 如果是第一次请求,返回null,并将 key 当前对象压入缓存中，第二次访问返回上次put的值
            if (CACHES.asMap().putIfAbsent(key,key) != null) {
                throw new RuntimeException("请勿重复请求");
            }
        }
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            log.error(">>>>>>>>>> LockMethodInterceptor proceed出现异常：", throwable);
            Integer resultCode = 500;
            // 补充拦截方法执行抛出非业务异常
            String errorDecs = throwable.getMessage();
            // 拦截方法执行抛出BusinessException业务异常
            if (throwable instanceof BusException){
                BusException be = (BusException) throwable;
                errorDecs = be.getMessage();
                resultCode = be.getCode();
            }
            throw new BusException(resultCode,errorDecs);
        } finally {
            //清除缓存
            CACHES.invalidate(key);
        }
    }

    /**
     * key 的生成策略
     *
     * @param keyExpress 表达式
     * @param args       参数
     * @return 生成的key
     */
    private String getKey(String keyExpress, Object[] args) {
        for (int i = 0; i < args.length; i++) {
            keyExpress = keyExpress.replace("arg[" + i + "]", args[i].toString());
        }
        return keyExpress;
    }

    /**
     * 通过spring Spel 获取参数
     *
     * @param key            定义的key值 以#开头 例如:#user
     * @param parameterNames 形参
     * @param values         形参值
     * @param keyConstant    key的常亮
     * @return
     */
    public String getVauleBySpel(String key, String[] parameterNames, Object[] values, String keyConstant) {
        StringBuilder keyBuilder = new StringBuilder();
        if (!key.contains("#")) {
            String s = "ad:locallock:" + keyConstant + key;
            log.info("没有使用spel表达式value->{}", s);
            keyBuilder.append(s);
            return keyBuilder.toString();
        }
        //spel解析器
        ExpressionParser parser = new SpelExpressionParser();
        //spel上下文
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], values[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) {
            if (value instanceof List) {
                List value1 = (List) value;
                keyBuilder.append("ad:locallock:").append(keyConstant);
                for (Object o : value1) {
                    keyBuilder.append(o.toString() + ",");
                }
            } else if (value.getClass().isArray()) {
                Object[] obj = (Object[]) value;
                keyBuilder.append("ad:locallock:").append(keyConstant);
                for (Object o : obj) {
                    keyBuilder.append(o.toString());
                }
            } else {
                keyBuilder.append("ad:locallock:");
                keyBuilder.append(keyConstant);
                keyBuilder.append(value.toString());
            }
        }
        log.info("spel表达式key={},value={}", key, keyBuilder.toString());
        return keyBuilder.toString();
    }
}